﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;

namespace Xceed.Silverlight.Data
{
  internal sealed class GroupCollection : IGroupCollection
  {
    #region Constructor

    internal GroupCollection()
    {
    }

    private GroupCollection( GroupCollection copy )
    {
      m_isCollapsedByDefault = copy.m_isCollapsedByDefault;
      m_items.AddRange( copy.m_items );
    }

    #endregion

    public GroupCollection Clone()
    {
      lock( m_syncRoot )
      {
        return new GroupCollection( this );
      }
    }

    public IGroupCollection AsReadOnly()
    {
      return new ReadOnlyGroupCollection( this );
    }

    public override int GetHashCode()
    {
      lock( m_syncRoot )
      {
        int hashCode = 11;

        hashCode += 17 * m_items.Count;
        hashCode += 17 * m_isCollapsedByDefault.GetHashCode();

        return hashCode;
      }
    }

    public override bool Equals( object obj )
    {
      if( object.ReferenceEquals( obj, this ) )
        return true;

      GroupCollection collection = obj as GroupCollection;

      if( object.ReferenceEquals( collection, null ) )
        return false;

      lock( m_syncRoot )
      {
        if( collection.GetHashCode() != this.GetHashCode() )
          return false;

        if( ( collection.m_isCollapsedByDefault != m_isCollapsedByDefault )
          || ( collection.m_items.Count != m_items.Count ) )
          return false;

        foreach( DataPath item in collection )
        {
          if( !m_items.Contains( item ) )
            return false;
        }
      }

      return true;
    }

    private void ValidateItem( DataPath item )
    {
      if( item == null )
        throw new ArgumentNullException( "item" );

      if( !( item.LastChild is GroupDataItem ) )
        throw new InvalidOperationException( "The path does not refer to a valid GroupDataItem." );
    }

    private void Clear( bool expandAll )
    {
      bool isCollapsedByDefault;

      lock( m_syncRoot )
      {
        isCollapsedByDefault = m_isCollapsedByDefault;

        // Make sure collection isn't already expanded or collapsed as request.
        if( ( isCollapsedByDefault != expandAll ) && ( m_items.Count == 0 ) )
          return;
      }

      GroupCollectionChangedEventArgs eventArgs;

      if( expandAll )
      {
        eventArgs = new GroupCollectionChangedEventArgs( GroupCollectionChangedAction.ExpandAll );
      }
      else
      {
        eventArgs = new GroupCollectionChangedEventArgs( GroupCollectionChangedAction.CollapseAll );
      }

      this.OnCollectionChanging( eventArgs );

      lock( m_syncRoot )
      {
        // The internal state has changed since last check.
        if( m_isCollapsedByDefault != isCollapsedByDefault )
          throw new InvalidOperationException();

        m_isCollapsedByDefault = !expandAll;
        m_items.Clear();
      }

      this.OnCollectionChanged( eventArgs );
    }

    private bool ExpandOrCollapse( DataPath item, bool expand )
    {
      this.ValidateItem( item );

      bool isCollapsedByDefault;

      lock( m_syncRoot )
      {
        // Make sure the item isn't already in the collection.
        if( ( expand ) == ( m_isCollapsedByDefault == m_items.Contains( item ) ) )
          return false;

        isCollapsedByDefault = m_isCollapsedByDefault;
      }

      GroupCollectionChangedEventArgs eventArgs;

      if( expand )
      {
        eventArgs = new GroupCollectionChangedEventArgs( GroupCollectionChangedAction.Expand, item );
      }
      else
      {
        eventArgs = new GroupCollectionChangedEventArgs( GroupCollectionChangedAction.Collapse, item );
      }

      this.OnCollectionChanging( eventArgs );

      lock( m_syncRoot )
      {
        // The internal state has changed since last check.
        if( m_isCollapsedByDefault != isCollapsedByDefault )
          throw new InvalidOperationException();

        if( m_isCollapsedByDefault == expand )
        {
          if( m_items.Contains( item ) )
            return false;

          m_items.Add( item );
        }
        else
        {
          if( !m_items.Remove( item ) )
            return false;
        }
      }

      this.OnCollectionChanged( eventArgs );

      return true;
    }

    private bool IsAlreadyIn( DataPath item, bool expand )
    {
      return ( ( expand ) == ( m_isCollapsedByDefault == m_items.Contains( item ) ) );
    }

    private void OnCollectionChanging( GroupCollectionChangedEventArgs e )
    {
      EventHandler<GroupCollectionChangedEventArgs> handlers;

      lock( m_syncRoot )
      {
        handlers = this.CollectionChanging;
      }

      if( handlers != null )
      {
        handlers.Invoke( this, e );
      }
    }

    private void OnCollectionChanged( GroupCollectionChangedEventArgs e )
    {
      EventHandler<GroupCollectionChangedEventArgs> handlers;

      lock( m_syncRoot )
      {
        handlers = this.CollectionChanged;
      }

      if( handlers != null )
      {
        handlers.Invoke( this, e );
      }
    }

    #region IGroupCollection Members

    public bool IsCollapsedByDefault
    {
      get
      {
        lock( m_syncRoot )
        {
          return m_isCollapsedByDefault;
        }
      }
    }

    public Nullable<bool> AnyCollapsedGroup
    {
      get
      {
        lock( m_syncRoot )
        {
          if( ( !m_isCollapsedByDefault ) && ( m_items.Count > 0 ) )
            return true;

          return null;
        }
      }
    }

    public Nullable<bool> AnyExpandedGroup
    {
      get
      {
        lock( m_syncRoot )
        {
          if( ( m_isCollapsedByDefault ) && ( m_items.Count > 0 ) )
            return true;

          return null;
        }
      }
    }

    public event EventHandler<GroupCollectionChangedEventArgs> CollectionChanging;
    public event EventHandler<GroupCollectionChangedEventArgs> CollectionChanged;

    public void CollapseAll()
    {
      this.Clear( false );
    }

    public void ExpandAll()
    {
      this.Clear( true );
    }

    public void Collapse( DataPath item )
    {
      this.ExpandOrCollapse( item, false );
    }

    public void Expand( DataPath item )
    {
      this.ExpandOrCollapse( item, true );
    }

    private bool m_isCollapsedByDefault; //false

    #endregion

    #region ICollection<DataPath> Members

    int ICollection<DataPath>.Count
    {
      get
      {
        lock( m_syncRoot )
        {
          return m_items.Count;
        }
      }
    }

    bool ICollection<DataPath>.IsReadOnly
    {
      get
      {
        return false;
      }
    }

    void ICollection<DataPath>.Clear()
    {
      this.Clear( true );
    }

    bool ICollection<DataPath>.Contains( DataPath item )
    {
      lock( m_syncRoot )
      {
        return m_items.Contains( item );
      }
    }

    void ICollection<DataPath>.Add( DataPath item )
    {
      this.ExpandOrCollapse( item, m_isCollapsedByDefault );
    }

    bool ICollection<DataPath>.Remove( DataPath item )
    {
      return this.ExpandOrCollapse( item, !m_isCollapsedByDefault );
    }

    void ICollection<DataPath>.CopyTo( DataPath[] array, int arrayIndex )
    {
      lock( m_syncRoot )
      {
        ICollection<DataPath> source = m_items;
        source.CopyTo( array, arrayIndex );
      }
    }

    #endregion

    #region ICollection Members

    object ICollection.SyncRoot
    {
      get
      {
        return m_syncRoot;
      }
    }

    bool ICollection.IsSynchronized
    {
      get
      {
        return true;
      }
    }

    int ICollection.Count
    {
      get
      {
        return ( ( ICollection<DataPath> )this ).Count;
      }
    }

    void ICollection.CopyTo( Array array, int index )
    {
      lock( m_syncRoot )
      {
        ICollection source = m_items;
        source.CopyTo( array, index );
      }
    }

    #endregion

    #region IEnumerable<DataPath> Members

    IEnumerator<DataPath> IEnumerable<DataPath>.GetEnumerator()
    {
      lock( m_syncRoot )
      {
        return new SafeEnumerator( m_items.GetEnumerator(), m_syncRoot );
      }
    }

    #endregion

    #region IEnumerable Members

    IEnumerator IEnumerable.GetEnumerator()
    {
      return ( ( IEnumerable<DataPath> )this ).GetEnumerator();
    }

    #endregion

    #region Private Fields

    private object m_syncRoot = new object();
    private readonly List<DataPath> m_items = new List<DataPath>();

    #endregion

    #region SafeEnumerator Nested Type

    private class SafeEnumerator : IEnumerator<DataPath>
    {
      internal SafeEnumerator( IEnumerator<DataPath> enumerator, object syncRoot )
      {
        if( enumerator == null )
          throw new ArgumentNullException( "enumerator" );

        if( syncRoot == null )
          throw new ArgumentNullException( "syncRoot" );

        m_enumerator = enumerator;
        m_syncRoot = syncRoot;
      }

      public DataPath Current
      {
        get
        {
          lock( m_syncRoot )
          {
            return m_enumerator.Current;
          }
        }
      }

      object IEnumerator.Current
      {
        get
        {
          return ( ( IEnumerator<DataPath> )this ).Current;
        }
      }

      public void Reset()
      {
        lock( m_syncRoot )
        {
          m_enumerator.Reset();
        }
      }

      public bool MoveNext()
      {
        lock( m_syncRoot )
        {
          return m_enumerator.MoveNext();
        }
      }

      void IDisposable.Dispose()
      {
        m_enumerator.Dispose();
      }

      private IEnumerator<DataPath> m_enumerator;
      private object m_syncRoot;
    }

    #endregion
  }
}
